﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using wwl.Contract.Interfaces.Rbac;
using wwl.SharedContracts.MultiTenancy;

namespace wwl.Infrastructure.MultiTenancy;

/// <summary>
/// 解析租户上下文中间件。
/// </summary>
/// <remarks>
/// - 从 Claims 或 Header 获取 TenantId。
/// - 若未提供，则视为系统管理员（TenantId = null）。
/// </remarks>
public class TenantResolutionMiddleware
{
  private readonly RequestDelegate _next;
  private readonly ITenantRepository _tenantRepository;

  public TenantResolutionMiddleware(RequestDelegate next, ITenantRepository tenantRepository){
    _next = next;
    _tenantRepository = tenantRepository;
  }

  public async Task InvokeAsync(HttpContext context)
  {
    try
    {
      // 1. 提取TenantId（优先级：Claims > 请求头）
      var tenantId = context.User.FindFirst("TenantId")?.Value
                   ?? context.Request.Headers["X-Tenant-Id"].FirstOrDefault();

      // 校验逻辑：非系统管理员（tenantId != null）时，确保租户存在且状态正常
      if (!string.IsNullOrEmpty(tenantId))
      {
        var tenant = await _tenantRepository.GetByIdAsync(tenantId);
        if (tenant == null || tenant.IsDeleted)
        {
          context.Response.StatusCode = StatusCodes.Status403Forbidden;
          await context.Response.WriteAsync("无效的租户ID或租户已禁用");
          return; // 终止请求，不执行后续逻辑
        }
      }

      // 2. 初始化租户上下文
      TenantContextAccessor.SetContext(tenantId); // null → 系统管理员

      // 3. 执行后续中间件/业务逻辑（如Controller、Service、Repository）
      await _next(context);
    }
    finally
    {
      // 4. 请求结束，强制清理上下文（关键：防止泄露）
      TenantContextAccessor.Clear();
    }
  }
}

/// <summary>
/// <see cref="TenantResolutionMiddleware"/> 的扩展方法，用于在应用程序请求管道中注册租户上下文解析中间件。
/// </summary>
/// <remarks>
/// <para>
/// 该中间件负责在每个 HTTP 请求开始时，解析当前操作者的租户身份，并初始化 <see cref="wwl.SharedContracts.MultiTenancy.TenantContextAccessor"/>。
/// 它支持多种租户识别方式，并自动将 <c>TenantId = null</c> 的上下文识别为**系统管理员（SystemManager）**。
/// </para>
/// <para>
/// 以下是三种典型使用场景：
/// </para>
/// <para>
/// <strong>场景 1：用户登录后访问接口（从 Claims 提取）</strong><br/>
/// 用户通过账号密码登录，认证服务生成 JWT Token，其中包含 Claims：{ "TenantId": "tenant-123" }。<br/>
/// 客户端后续请求携带 <c>Authorization: Bearer {Token}</c> 头，ASP.NET Core 认证中间件解析 Token 并填充 <c>context.User.Claims</c>。<br/>
/// 本中间件从 <c>context.User</c> 中提取到 <c>TenantId = "tenant-123"</c>，并初始化租户上下文。<br/>
/// 后续的 Controller、Service 或数据访问层可通过 <see cref="wwl.SharedContracts.MultiTenancy.TenantContextAccessor.TenantId"/> 获取该值，<br/>
/// EF Core 全局查询过滤器将自动仅返回该租户的数据。
/// </para>
/// <para>
/// <strong>场景 2：API 调试（从请求头提取）</strong><br/>
/// 开发者在 Postman 或 Swagger 中添加请求头 <c>X-Tenant-Id: tenant-456</c>，未携带 JWT Token。<br/>
/// 本中间件从请求头 <c>X-Tenant-Id</c> 中提取 <c>TenantId = "tenant-456"</c>，并初始化上下文。<br/>
/// 此机制允许开发者在无需登录的情况下，直接指定目标租户进行接口调试，极大提升开发效率。
/// </para>
/// <para>
/// <strong>场景 3：系统管理员操作（TenantId 为 null）</strong><br/>
/// 系统管理员登录后，其 JWT Token 的 Claims 中**未包含** <c>TenantId</c>（或请求未传递 <c>X-Tenant-Id</c> 头）。<br/>
/// 本中间件提取到 <c>tenantId = null</c>，调用 <see cref="wwl.SharedContracts.MultiTenancy.TenantContextAccessor.SetContext(string?)"/> 传入 <see langword="null"/>。<br/>
/// 此时 <see cref="wwl.SharedContracts.MultiTenancy.TenantContextAccessor.IsSystemAdmin"/> 自动设为 <see langword="true"/>。<br/>
/// 数据访问层的全局查询过滤器将跳过租户限制，系统管理员可访问**所有租户的数据**，实现平台级管理功能。
/// </para>
/// </remarks>
/// <param name="app">应用程序构建器实例。</param>
/// <returns>配置后的应用程序构建器，支持链式调用。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="app"/> 为 <see langword="null"/> 时抛出。</exception>
/// <seealso cref="TenantResolutionMiddleware"/>
public static class TenantResolutionMiddlewareExtensions
{
  public static IApplicationBuilder UseTenantResolution(this IApplicationBuilder app)
      => app.UseMiddleware<TenantResolutionMiddleware>();
}